﻿using System;
using System.Runtime.Serialization;

namespace Heimdallr
{
    /// <summary>
    /// Define the basic element in service. This is an abstract class.
    /// </summary>
    public abstract class ServiceElement
    {
        #region Field

        private ServiceElementStatus status;
        private bool canPauseAndContinue;

        #endregion

        #region Property

        /// <summary>
        /// Gets the status of this element. The default value is <see cref="ServiceElementStatus">Stopped</see>.
        /// </summary>
        /// <remarks>Changing value of this property in derived classes will raise <see cref="StatusChanged"/> event.</remarks>
        public ServiceElementStatus Status
        {
            get { return this.status; }
            protected set
            {
                if (this.status == value)
                    return;

                ServiceElementStatus oldStatus = this.status;

                this.status = value;
                this.OnStatusChanged(new StatusChangedEventArgs(oldStatus, value));
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the element can be paused and resumed. The default value is false.
        /// </summary>
        /// <exception cref="InvalidOperationException">This property cannot be changed once the element has started.</exception>
        public bool CanPauseAndContinue
        {
            get { return this.canPauseAndContinue; }
            protected set
            {
                if (this.status != ServiceElementStatus.Stopped)
                    throw new InvalidOperationException("This property cannot be changed once the service has started.");

                this.canPauseAndContinue = value;
            }
        }

        #endregion

        #region Event

        /// <summary>
        /// Occurs when encounters unavoidable exceptions and must stops.
        /// </summary>
        public event EventHandler<ExceptionsEventArgs> Error;

        /// <summary>
        /// Occurs when property <see cref="Status"/> value changes.
        /// </summary>
        public event EventHandler<StatusChangedEventArgs> StatusChanged;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the ServiceElement class.
        /// </summary>
        protected ServiceElement()
        {
            this.status = ServiceElementStatus.Stopped;
            this.canPauseAndContinue = false;
        }

        #endregion

        #region Internal Method

        internal void Run()
        {
            this.ChangeStatus(this.OnStart, ServiceElementStatus.StartPending, ServiceElementStatus.Running);
        }

        internal void Stop()
        {
            this.ChangeStatus(this.OnStop, ServiceElementStatus.StopPending, ServiceElementStatus.Stopped);
        }

        internal void Pause()
        {
            this.ChangeStatus(this.OnPause, ServiceElementStatus.PausePending, ServiceElementStatus.Paused);
        }

        internal void Continue()
        {
            this.ChangeStatus(this.OnContinue, ServiceElementStatus.ContinuePending, ServiceElementStatus.Running);
        }

        #endregion

        #region Protected Virtual Method

        /// <summary>
        /// When implemented in a derived class,
        /// executes when the status of element is changing from <see cref="ServiceElementStatus">Stopped</see> to <see cref="ServiceElementStatus">Running</see>.
        /// </summary>
        protected abstract void OnStart();

        /// <summary>
        /// When implemented in a derived class,
        /// executes when the status of element is changing from <see cref="ServiceElementStatus">Running</see> to <see cref="ServiceElementStatus">Stopped</see>.
        /// </summary>
        protected abstract void OnStop();

        /// <summary>
        /// When overriden in a derived class,
        /// executes when the status of element is changing from <see cref="ServiceElementStatus">Running</see> to <see cref="ServiceElementStatus">Paused</see>.
        /// </summary>
        protected virtual void OnPause() { }

        /// <summary>
        /// When overriden in a derived class,
        /// executes when the status of element is changing from <see cref="ServiceElementStatus">Paused</see> to <see cref="ServiceElementStatus">Running</see>.
        /// </summary>
        protected virtual void OnContinue() { }

        #endregion

        #region Raise Event

        /// <summary>
        /// Raises the <see cref="Error"/> event.
        /// </summary>
        /// <param name="e">Event data for the event.</param>
        /// <exception cref="ArgumentNullException">Parameter <paramref name="e"/> is null.</exception>
        /// <remarks>
        /// Derived classes should invoke this method when encounters unavoidable exceptions and must stops.
        /// This will also sets property <see cref="Status"/> to <see cref="ServiceElementStatus">Stopped</see>.
        /// </remarks>
        protected virtual void OnError(ExceptionsEventArgs e)
        {
            if (e == null)
                throw new ArgumentNullException("e");

            if (this.Error != null)
                this.Error(this, e);

            this.Status = ServiceElementStatus.Stopped;
        }

        private void OnStatusChanged(StatusChangedEventArgs e)
        {
            if (e == null)
                throw new ArgumentNullException("e");

            if (this.StatusChanged != null)
                this.StatusChanged(this, e);
        }

        #endregion

        #region Other

        private void ChangeStatus(Action method, ServiceElementStatus beforeStatus, ServiceElementStatus afterStatus)
        {
            try
            {
                method.BeginInvoke(this.ChangeStatusCallback, Tuple.Create(method, beforeStatus, afterStatus));
            }
            catch (Exception error)
            {
                this.Status = ServiceElementStatus.Stopped;
                this.OnError(new ExceptionsEventArgs(error));
            }
        }

        private void ChangeStatusCallback(IAsyncResult r)
        {
            var asyncState = r.AsyncState as Tuple<Action, ServiceElementStatus, ServiceElementStatus>;

            try
            {
                this.Status = asyncState.Item2;
                asyncState.Item1.EndInvoke(r);
                this.Status = asyncState.Item3;
            }
            catch (Exception error)
            {
                this.Status = ServiceElementStatus.Stopped;
                this.OnError(new ExceptionsEventArgs(error));
            }
        }

        #endregion
    }

    /// <summary>
    /// Indicates the current state of the service element.
    /// </summary>
    [DataContract]
    public enum ServiceElementStatus
    {
        /// <summary>
        /// The service element continue is pending.
        /// </summary>
        [EnumMember]
        ContinuePending,

        /// <summary>
        /// The service element is paused.
        /// </summary>
        [EnumMember]
        Paused,

        /// <summary>
        /// The service pause is pending.
        /// </summary>
        [EnumMember]
        PausePending,

        /// <summary>
        /// The service element is running.
        /// </summary>
        [EnumMember]
        Running,

        /// <summary>
        /// The service element is starting.
        /// </summary>
        [EnumMember]
        StartPending,

        /// <summary>
        /// The service element is stopped.
        /// </summary>
        [EnumMember]
        Stopped,

        /// <summary>
        /// The service element is stopping.
        /// </summary>
        [EnumMember]
        StopPending
    }
}